package com.hmdp.service.impl;

import com.hmdp.config.RedissonConfig;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleReidLock;
import com.hmdp.utils.UserHolder;
import org.redisson.api.RLock;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    //    唯一id生产器工具类
    @Resource
    private RedisIdWorker redisIdWorker;
    //    秒杀优惠卷接口
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    //    redisson
    @Resource
    public RedissonConfig redissonClient;
    //    创建一个阻塞队列  指定内存为1024*1024。
    private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
    //    创建一个线程池
    private static final ExecutorService SECKILL_ORDER__EXECUTOR = Executors.newSingleThreadExecutor();

    /**
     * 该方法在类的构造完成后执行，用于初始化过程。 @PostConstruct 注解的作用。
     * 主要提交一个代金券订单处理任务到线程池中执行。
     * 该方法没有参数和返回值。
     */
    @PostConstruct
    private void init() {
//        类初始化后执行
        SECKILL_ORDER__EXECUTOR.submit(new VoucherOrderHandler());
    }

    @Override
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        if (query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count() > 0) {
            log.error("用户已经购买过一次了");
        }
        boolean success = seckillVoucherService.update()
                .setSql("stock= stock-1")
                .eq("voucher_id", voucherOrder.getVoucherId())
                .gt("stock", 0)
                .update();
        if (!success) {
            log.error("库存不足");
        }
//      创建订单
        save(voucherOrder);
    }

    private class VoucherOrderHandler implements Runnable {
        @Override
        public void run() {
//            因为阻塞队列是线程安全的，所以可以多线程处理。
//            不会死循环 阻塞队列为空时，take方法会阻塞。所以不会产生无线循环。
            while (true) {
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //2.调用方法 创建订单
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("处理订单异常", e);
                }

            }
        }

        /**
         * 处理订单 将订单持久化到数据库中
         */
        private void handleVoucherOrder(VoucherOrder voucherOrder) {
//            获取用户
            Long userId = voucherOrder.getUserId();
//            创建锁对象
            RLock lock = redissonClient.redissonClient().getLock("lock:order:" + userId);
//             获取锁
            boolean isLock = lock.tryLock();
//             如果获取锁失败
            if (!isLock) {
                log.error("获取锁失败");
            }
            try {
                proxy.createVoucherOrder(voucherOrder);
            } finally {
                lock.unlock();
            }
        }
    }

    private static final DefaultRedisScript<Long> SECKIL_SCRIPT;

    static {
        SECKIL_SCRIPT = new DefaultRedisScript<>();
        SECKIL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKIL_SCRIPT.setResultType(Long.class);
    }

    //     创建代理对象
    private IVoucherOrderService proxy;

    @Override
    public Result seckillVoucherLua(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
//        1执行lua脚本 1库存不足 2重复下单
        Long aLong = stringRedisTemplate.execute(SECKIL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                String.valueOf(userId)
        );
//        2判断结果是否为0
        if (aLong != 0) {
//        2.1 为0 表示有购买资格
            return Result.fail(aLong == 1 ? "库存不足" : "不能重复下单");
        }
//        2.2为0 表示有购买资格 获取全局唯一id
        Long orderId = redisIdWorker.nextId("order");
//        保存到阻塞消息队列
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setId(orderId);
        orderTasks.add(voucherOrder);
//      获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
//        3返回订单id
        return Result.ok(orderId);
    }

    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
//       1查询优惠券
        SeckillVoucher kil = seckillVoucherService.getById(voucherId);
//        2判断秒杀是否开始,开始时间大于当前时间表示未开始
        if (kil.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("优惠卷抢购时间未开始");
        }
//        3判断秒杀活动是否结束,结束时间小于当前时间表示秒杀活动已经结束
        if (kil.getBeginTime().isBefore(LocalDateTime.now())) {
            Result.fail("秒杀活动已近结束");
        }
//        4判断库存是否充足
        if (kil.getStock() < 1) {
            Result.fail("库存不足");
        }
//        获取锁
        SimpleReidLock lock = new SimpleReidLock("oder:" + userId, stringRedisTemplate);

//        设置超时时间，时间与执行业务所需时间有关，一般为业务所需时间10倍
        try {
            boolean isTryLock = lock.tryLock(10);
            if (!isTryLock) {
//             获取锁失败，返回错误或者重试。
                return Result.fail("一种优惠卷，只能购买一次");
            }
            return createOrder(voucherId, userId);
        } finally {
            lock.unLock();
        }
    }

    /**
     * 创建订单
     *
     * @param voucherId 优惠券id
     * @param userId    用户id
     * @return
     */
//        @Transactional 事物，这里有两张表的操作，所以要加事物。让库存扣减与下单同时执行。
    @Transactional
    public Result createOrder(Long voucherId, Long userId) {
//      给相同用户加同步锁。
        synchronized (userId.toString().intern()) {
//          一人一单判断，同一个人同一种优惠卷只能买一张
            Integer count = query().eq("userId", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                Result.fail("一人只能购买一张优惠劵");
            }
//        5优惠卷数量减一
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock-1")
                    .eq("voucher_id", voucherId)
//                乐观锁，在数据库上加一个版本号，修改前先核对版本号，如果未被修改就进行修，在处理超卖业务时，只要判断库存是否大于0就行了
//                避免超卖
                    .gt("stock", 0)
                    .update();
            if (!success) {
                return Result.fail("库存不足");
            }
//         6创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            Long id = redisIdWorker.nextId("oder");
//        订单id
            voucherOrder.setId(id);
//        优惠卷id
            voucherOrder.setVoucherId(voucherId);
//        用户id
            voucherOrder.setUserId(userId);
            save(voucherOrder);
//        8返回订单id
            return Result.ok("抢购成功：订单id：" + id);
        }
    }
}
